#include "GameObject.h"
#include "Scene.h"
#include "Exception.h"
#include "Logger.h"

using namespace game_engine;
using namespace game_engine::utils;

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

GameObject::GameObject(void)
    : name(), tag(), drawLevel(ENTITY_LEVEL), parent(NULL), scene(NULL)
{
    children.clear();
    components.clear();
}

GameObject::GameObject(Scene *scn, const string &n, const string &t, 
                       const DrawLevelType d)
    : name(n), tag(t), drawLevel(d), parent(NULL), scene(scn)
{
    children.clear();
    components.clear();
}

GameObject::~GameObject(void)
{
    // delete children
    children.clear();

    // delete components
    components.clear();
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

void GameObject::addChild(GameObject *newChild) throw(...)
{
    map<string, GameObject*>::const_iterator it;
    if (findChild(newChild->getName(), it))
    {
        string err("object ");
        err.append(newChild->getName());
        err.append(" already exists as child of object ");
        err.append(name);
        throw IllegalArgumentException(err);
    }
    children[newChild->getName()] = newChild;
}

void GameObject::addComponent(Component *comp) throw(...)
{
    map<string, Component*>::const_iterator it;
    //if (findComponent(comp->getType(), it))
    //{
    //    string err("component of type ");
    //    err.append(comp->getType());
    //    err.append(" already exists as part of the object ");
    //    err.append(name);
    //    throw IllegalArgumentException(err);
    //}
    components[comp->getType()] = comp;
}

void GameObject::removeComponent(const string &compType) throw(...)
{
    if (!hasComponent(compType))
    {
        string err("component of type ");
        err.append(compType);
        err.append(" not found");
        throw NoSuchElementException(err);
    }
    components.erase(compType);
}

const BOOL GameObject::operator<(const GameObject &other) const
{
    if (drawLevel < other.getDrawLevel())
    {
        return true;
    }
    else
    {
        return false;
    }
}

const BOOL GameObject::hasComponent(const string &compType)
{
    BOOL found = false;
    map<string, Component*>::const_iterator it;
    if (findComponent(compType, it))
    {
        found = true;
    }
    return found;
}

void GameObject::initialize(void)
{
    map<string, Component*>::const_iterator it;
    for(it=components.begin(); it!=components.end(); it++)
    {
        ((*it).second)->initialize();
    }
}

void GameObject::update(const FLOAT32 time)
{
    map<string, Component*>::const_iterator it;
    for(it=components.begin(); it!=components.end(); it++)
    {
        ((*it).second)->update(time);
    }
}

void GameObject::render(void)
{
    map<string, Component*>::const_iterator it;
    for(it=components.begin(); it!=components.end(); it++)
    {
        ((*it).second)->render();
    }
}

void GameObject::finalize(void)
{
    map<string, Component*>::const_iterator it;
    for(it=components.begin(); it!=components.end(); it++)
    {
        ((*it).second)->finalize();
    }
}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

const BOOL GameObject::findChild(const string &childName, 
                                 map<string, GameObject*>::const_iterator &it) 
                                 const
{
    BOOL found = false;
    it = children.find(childName);
    if (it != children.end())
    {
        found = true;
    }
    return found;
}

const BOOL GameObject::findComponent(const string &compType, 
                                     map<string, Component*>::const_iterator &it) 
                                     const
{
    BOOL found = false;
    it = components.find(compType);
    if (it != components.end())
    {
        found = true;
    }
    return found;
}

///////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////

const string& GameObject::getName(void) const
{
    return name;
}

const string& GameObject::getTag(void) const
{
    return tag;
}

const DrawLevelType GameObject::getDrawLevel(void) const
{
    return drawLevel;
}

GameObject* GameObject::getParent(void) const
{
    return parent;
}

GameObject* GameObject::getChild(const string &childName) const throw(...)
{
    map<string, GameObject*>::const_iterator it;
    if (!findChild(childName, it))
    {
        string err("child object ");
        err.append(childName);
        err.append(" not found");
        throw NoSuchElementException(err);
    }
    return (*it).second;
}

Component* GameObject::getComponent(const string &compType) const throw(...)
{
    map<string, Component*>::const_iterator it;
    if (!findComponent(compType, it))
    {
        string err("component of type ");
        err.append(compType);
        err.append(" not found");
        throw NoSuchElementException(err);
    }
    return (*it).second;
}

Scene* GameObject::getScene(void) const
{
    return scene;
}

///////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////

void GameObject::setName(const string &n)
{
    name = n;
}

void GameObject::setTag(const string &t)
{
    tag = t;
}

void GameObject::setDrawLevel(const DrawLevelType level)
{
    drawLevel = level;
}

void GameObject::setParent(GameObject *p)
{
    parent = p;
}

void GameObject::setScene(Scene *s)
{
    scene = s;
}
